/*
 * Copyright (c) 2000, 2003, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */
package com.sun.corba.se.impl.encoding;

import java.nio.ByteBuffer;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.LinkedList;

import com.sun.corba.se.impl.encoding.BufferQueue;
import com.sun.corba.se.impl.encoding.BufferManagerWrite;
import com.sun.corba.se.impl.orbutil.ORBConstants;
import com.sun.corba.se.impl.protocol.giopmsgheaders.Message;
import com.sun.corba.se.impl.encoding.ByteBufferWithInfo;
import com.sun.corba.se.impl.protocol.giopmsgheaders.MessageBase;
import com.sun.corba.se.impl.protocol.giopmsgheaders.FragmentMessage;
import com.sun.corba.se.spi.orb.ORB;
import com.sun.corba.se.impl.encoding.CDROutputObject;
import com.sun.corba.se.impl.orbutil.ORBUtility;
import com.sun.corba.se.pept.transport.Connection;
import com.sun.corba.se.pept.transport.ByteBufferPool;
import com.sun.corba.se.pept.encoding.OutputObject;

/**
 * Collect buffer manager.
 */
public class BufferManagerWriteCollect extends BufferManagerWrite {

  private BufferQueue queue = new BufferQueue();

  private boolean sentFragment = false;
  private boolean debug = false;


  BufferManagerWriteCollect(ORB orb) {
    super(orb);
    if (orb != null) {
      debug = orb.transportDebugFlag;
    }
  }

  public boolean sentFragment() {
    return sentFragment;
  }

  /**
   * Returns the correct buffer size for this type of
   * buffer manager as set in the ORB.
   */
  public int getBufferSize() {
    return orb.getORBData().getGIOPFragmentSize();
  }

  // Set the fragment's "more fragments" bit to true, put it in the
  // queue, and allocate a new bbwi.
  public void overflow(ByteBufferWithInfo bbwi) {
    // Set the fragment's moreFragments field to true
    MessageBase.setFlag(bbwi.byteBuffer, Message.MORE_FRAGMENTS_BIT);

    // Enqueue the previous fragment
    queue.enqueue(bbwi);

    // Create a new bbwi
    ByteBufferWithInfo newBbwi = new ByteBufferWithInfo(orb, this);
    newBbwi.fragmented = true;

    // XREVISIT - Downcast
    ((CDROutputObject) outputObject).setByteBufferWithInfo(newBbwi);

    // Now we must marshal in the fragment header/GIOP header

    // REVISIT - we can optimize this by not creating the fragment message
    // each time.

    // XREVISIT - Downcast
    FragmentMessage header =
        ((CDROutputObject) outputObject).getMessageHeader()
            .createFragmentMessage();

    header.write((CDROutputObject) outputObject);
  }

  // Send all fragments
  public void sendMessage() {
    // Enqueue the last fragment
    queue.enqueue(((CDROutputObject) outputObject).getByteBufferWithInfo());

    Iterator bufs = iterator();

    Connection conn =
        ((OutputObject) outputObject).getMessageMediator().
            getConnection();

    // With the collect strategy, we must lock the connection
    // while fragments are being sent.  This is so that there are
    // no interleved fragments in GIOP 1.1.
    //
    // Note that this thread must not call writeLock again in any
    // of its send methods!
    conn.writeLock();

    try {

      // Get a reference to ByteBufferPool so that the ByteBufferWithInfo
      // ByteBuffer can be released to the ByteBufferPool
      ByteBufferPool byteBufferPool = orb.getByteBufferPool();

      while (bufs.hasNext()) {

        ByteBufferWithInfo bbwi = (ByteBufferWithInfo) bufs.next();
        ((CDROutputObject) outputObject).setByteBufferWithInfo(bbwi);

        conn.sendWithoutLock(((CDROutputObject) outputObject));

        sentFragment = true;

        // Release ByteBufferWithInfo's ByteBuffer back to the pool
        // of ByteBuffers.
        if (debug) {
          // print address of ByteBuffer being released
          int bbAddress = System.identityHashCode(bbwi.byteBuffer);
          StringBuffer sb = new StringBuffer(80);
          sb.append("sendMessage() - releasing ByteBuffer id (");
          sb.append(bbAddress).append(") to ByteBufferPool.");
          String msg = sb.toString();
          dprint(msg);
        }
        byteBufferPool.releaseByteBuffer(bbwi.byteBuffer);
        bbwi.byteBuffer = null;
        bbwi = null;
      }

      sentFullMessage = true;

    } finally {

      conn.writeUnlock();
    }
  }

  /**
   * Close the BufferManagerWrite - do any outstanding cleanup.
   *
   * For a BufferManagerWriteGrow any queued ByteBufferWithInfo must
   * have its ByteBuffer released to the ByteBufferPool.
   */
  public void close() {
    // iterate thru queue and release any ByteBufferWithInfo's
    // ByteBuffer that may be remaining on the queue to the
    // ByteBufferPool.

    Iterator bufs = iterator();

    ByteBufferPool byteBufferPool = orb.getByteBufferPool();

    while (bufs.hasNext()) {
      ByteBufferWithInfo bbwi = (ByteBufferWithInfo) bufs.next();
      if (bbwi != null && bbwi.byteBuffer != null) {
        if (debug) {
          // print address of ByteBuffer being released
          int bbAddress = System.identityHashCode(bbwi.byteBuffer);
          StringBuffer sb = new StringBuffer(80);
          sb.append("close() - releasing ByteBuffer id (");
          sb.append(bbAddress).append(") to ByteBufferPool.");
          String msg = sb.toString();
          dprint(msg);
        }
        byteBufferPool.releaseByteBuffer(bbwi.byteBuffer);
        bbwi.byteBuffer = null;
        bbwi = null;
      }
    }
  }

  private void dprint(String msg) {
    ORBUtility.dprint("BufferManagerWriteCollect", msg);
  }

  private Iterator iterator() {
    return new BufferManagerWriteCollectIterator();
  }

  private class BufferManagerWriteCollectIterator implements Iterator {

    public boolean hasNext() {
      return queue.size() != 0;
    }

    public Object next() {
      return queue.dequeue();
    }

    public void remove() {
      throw new UnsupportedOperationException();
    }
  }
}
